﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Windows.Forms;
using System.Reflection;
using System.IO;
using System.Runtime.InteropServices;
using ExtendedDotNET.Controls;
using System.Diagnostics;


namespace Treasures
{
    public partial class MainWindowForm : Form
    {
        #region imports

        [DllImport("user32.dll")]
        public static extern bool GetCursorPos(out Point lpPoint);

        #endregion

        #region coustructor

        public MainWindowForm()
        {
            InitializeComponent();
        }

        #endregion

        #region Fields and Props
        DoubleBufferedPanel panel1;
       // Label tb = new Label();
        ExtendedDotNET.Controls.Progress.ProgressBar progressBar1;
        ExtendedDotNET.Controls.Progress.ProgressBar progressBar2;

        public int TimerCount { get; set; }
        bool isRefreshed { get; set; }
        
        #endregion

        #region events
                
        #if DEBUG
        public static event Action FreeAll;
        #endif

        #endregion

        #region event handlers
        private void OnCreate(object sender, EventArgs e)
        {
            this.ClientSize = Constants.initialClientSize;
            
            // 
            // panel1
            // 
            panel1 = new DoubleBufferedPanel();
            this.panel1.Location = Constants.initialLocation;
            this.panel1.Name = "panel1";
            this.panel1.Size = this.ClientSize;
                //new System.Drawing.Size(2 * Constants.Border + (Constants.distanceBetweenElements + Constants.initialElementWidth) * Constants.initialElementsInRow, 
                //2 * Constants.Border + (Constants.distanceBetweenElements + Constants.initialElementWidth) * Constants.initialElementsInRow);
            this.panel1.TabIndex = 0;
            this.panel1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnMouseDown);
            this.panel1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnMouseDragTheElement);
            this.Controls.Add(panel1);


            // 
            // progressBar1
            // 
            progressBar1 = new ExtendedDotNET.Controls.Progress.ProgressBar();
            this.progressBar1.BarOffset = 0;
            this.progressBar1.Caption = "Time";
            this.progressBar1.CaptionColor = System.Drawing.Color.Black;
            this.progressBar1.CaptionMode = ExtendedDotNET.Controls.Progress.ProgressCaptionMode.Custom;
            this.progressBar1.CaptionShadowColor = System.Drawing.Color.White;
            this.progressBar1.ChangeByMouse = false;
            this.progressBar1.DashSpace = 0;
            this.progressBar1.DashWidth = 0;
            this.progressBar1.Edge = ExtendedDotNET.Controls.Progress.ProgressBarEdge.Rectangle;
            this.progressBar1.EdgeColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(241)))), ((int)(((byte)(150)))));
            this.progressBar1.EdgeLightColor = System.Drawing.Color.Empty;
            this.progressBar1.EdgeWidth = 1;
            this.progressBar1.FloodPercentage = 0F;
            this.progressBar1.FloodStyle = ExtendedDotNET.Controls.Progress.ProgressFloodStyle.Standard;
            this.progressBar1.Font = new System.Drawing.Font("Arial", 10.5F, System.Drawing.FontStyle.Regular);
            this.progressBar1.Invert = false;
            this.progressBar1.Location = new System.Drawing.Point(820, 500);
            this.progressBar1.MainColor = System.Drawing.Color.FromArgb(((int)(((byte)(115)))), ((int)(((byte)(107)))), ((int)(((byte)(50)))));
            this.progressBar1.Maximum = 5;
            this.progressBar1.Minimum = 0;
            this.progressBar1.Name = "time progress";
            this.progressBar1.Orientation = ExtendedDotNET.Controls.Progress.ProgressBarDirection.Vertical;
            this.progressBar1.ProgressBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(221)))), ((int)(((byte)(201)))), ((int)(((byte)(69)))));
            this.progressBar1.ProgressBarStyle = ExtendedDotNET.Controls.Progress.ProgressStyle.Solid;
            this.progressBar1.SecondColor = System.Drawing.Color.White;
            this.progressBar1.Shadow = false;
            this.progressBar1.ShadowOffset = 1;
            this.progressBar1.Size = new System.Drawing.Size(45, 150);
            this.progressBar1.Step = 1;
            this.progressBar1.TextAntialias = true;
            this.progressBar1.Value = 0;
            this.panel1.Controls.Add(progressBar1);


            // 
            // progressBar2
            // 
            progressBar2 = new ExtendedDotNET.Controls.Progress.ProgressBar();
            this.progressBar2.BarOffset = 0;
            this.progressBar2.Caption = "0 / 5";
            this.progressBar2.CaptionColor = System.Drawing.Color.Black;
            this.progressBar2.CaptionMode = ExtendedDotNET.Controls.Progress.ProgressCaptionMode.Custom;
            this.progressBar2.CaptionShadowColor = System.Drawing.Color.White;
            this.progressBar2.ChangeByMouse = false;
            this.progressBar2.DashSpace = 0;
            this.progressBar2.DashWidth = 0;
            this.progressBar2.Edge = ExtendedDotNET.Controls.Progress.ProgressBarEdge.Rectangle;
            this.progressBar2.EdgeColor = System.Drawing.Color.FromArgb(((int)(((byte)(188)))), ((int)(((byte)(188)))), ((int)(((byte)(188)))));
            this.progressBar2.EdgeLightColor = System.Drawing.Color.Empty;
            this.progressBar2.EdgeWidth = 1;
            this.progressBar2.FloodPercentage = 0F;
            this.progressBar2.FloodStyle = ExtendedDotNET.Controls.Progress.ProgressFloodStyle.Standard;
            this.progressBar2.Font = new System.Drawing.Font("Arial", 10.5F, System.Drawing.FontStyle.Regular);
            this.progressBar2.Invert = false;
            this.progressBar2.Location = new System.Drawing.Point(28, 730);
            this.progressBar2.MainColor = System.Drawing.Color.FromArgb(((int)(((byte)(6)))), ((int)(((byte)(176)))), ((int)(((byte)(37)))));
            this.progressBar2.Maximum = 5;
            this.progressBar2.Minimum = 0;
            this.progressBar2.Name = "crystal progress";
            this.progressBar2.Orientation = ExtendedDotNET.Controls.Progress.ProgressBarDirection.Horizontal;
            this.progressBar2.ProgressBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));
            this.progressBar2.ProgressBarStyle = ExtendedDotNET.Controls.Progress.ProgressStyle.Solid;
            this.progressBar2.SecondColor = System.Drawing.Color.White;
            this.progressBar2.Shadow = false;
            this.progressBar2.ShadowOffset = 1;
            this.progressBar2.Size = new System.Drawing.Size(670, 16);
            this.progressBar2.Step = 1;
            this.progressBar2.TextAntialias = true;
            this.progressBar2.Value = 0;
            this.panel1.Controls.Add(progressBar2);

            

            #if DEBUG
            this.FormClosing += new FormClosingEventHandler((x, y) => { FreeAll();  });
            #endif
            this.CrystalInfo.Text = String.Format("Level = {0}\nCrystals = {1} / {2}", ElementMap.Instance.LControl.Level, ElementMap.Instance.LControl.CurrentCrystalScore, ElementMap.Instance.LControl.Level * 5);
            this.tmrRefresh.Start();
            this.FpsTimer.Start();
            TimerCount = Constants.initialTime;
            ElementMap.Instance.StartTimer += new Action(() => 
            {
                if (this.IsHandleCreated)
                {
                    this.BeginInvoke(new Action(() =>
                    {
                        this.progressBar1.Maximum = Constants.initialTime + 10 * ElementMap.Instance.LControl.Level * ElementMap.Instance.NumberOfColours;
                        this.progressBar1.Value = 0;
                        MainTimer.Start();
                    }));
                }
            });


            ElementMap.Instance.ChangeRefreshState += new Action(() =>
            {
                this.isRefreshed = false;
            });

            //elements
            ElementMap map = ElementMap.Instance;
            

            //crystal info refresh
            ElementMap.Instance.LControl.OnCrystalInfoChange += new Action(() => this.OnCrystalInfoChanged());
            ElementMap.Instance.LControl.OnPointsInfoChange += new Action<Point, int>((p, x) => this.OnPointsInfoChanged(p, x));

            //drop if there are no elements for some reason (sometimes we have that bug). And its not a proper solution for it.
            OnClickDropButton(null, null);
            Thread th = new Thread(new ThreadStart(new Action(() =>
            {
                ElementMap.Instance.OnGenerateNewMap(false, true);

            })));
            th.Start();
            
            
            #region debug info output
            #endregion
        }
       
        private void OnTimerTickRefresh(object sender, EventArgs e)
        {
            if (isRefreshed == false)
                isRefreshed = false;

            bool b = ElementMap.Instance.AnyBusy();
            if (!b && !isRefreshed)
            {
                if (ElementMap.Instance.ColorMap.Count > 0)
                {
                    for (int i = 0; i < ElementMap.Instance.ColorMap.Peek().GetLength(0); i++)
                    {
                        for (int j = 0; j < ElementMap.Instance.ColorMap.Peek().GetLength(0); j++)
                        {
                            ElementMap.Instance.PointMap[i, j] = ElementMap.Instance.initialPointMap[i, j];
                        }
                    }
                }
            }
            isRefreshed = true;
            this.Invalidate(true);
            panel1.FPSCounter++;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            return;
        }


        protected override void OnPaintBackground(PaintEventArgs e)
        {
            return;
        }

        private void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            ElementMap.Instance.ProcessUserAction(sender, e);
        }

        private void OnMouseDragTheElement(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            Panel panel = sender as Panel;
            Point pos = new Point();
            if (!GetCursorPos(out pos))
            {
                throw new InvalidOperationException("Error in GetCursorPos() in OnMouseDragTheElement.");
            }


            Point clientPoint;
            //core logic of method

            // moving left for elements right to 1st coloumn

            clientPoint = panel.PointToClient(pos);
            Point elementIndex = ElementMap.Instance.GetIndexByCoords(clientPoint.X, clientPoint.Y);
            if (e.Button == MouseButtons.Left && ElementMap.Instance.Selection.X != -1)
            {
                if (ElementMap.Instance.Check(ElementMap.Instance.Selection, elementIndex) && !(ElementMap.Instance.BusyMap[elementIndex.X, elementIndex.Y] || ElementMap.Instance.BusyMap[ElementMap.Instance.Selection.X, ElementMap.Instance.Selection.Y]))
                {
                   // tb.Text += String.Format("Drag\n");
                    OnMouseDown(panel, new System.Windows.Forms.MouseEventArgs(MouseButtons.None, 1, clientPoint.X, clientPoint.Y, 0));
                    return;
                }
            }
            else
            {
                if (elementIndex.X != -2)
                {
                    if (!panel1.Reset1Performed)
                    {
                        panel1.resetButton1.CurrentBitmap = 0;
                    }
                }

                if (elementIndex.X != -3)
                {
                    if (!panel1.Reset1Performed)
                    {
                        panel1.resetButton2.CurrentBitmap = 0;
                    }
                }

                if (elementIndex.X == -2)
                {
                    if (!panel1.Reset1Performed)
                    {
                        panel1.resetButton1.CurrentBitmap = 1;
                    }
                    return;
                }
                if (elementIndex.X == -3)
                {
                    if (!panel1.Reset2Performed)
                    {
                        panel1.resetButton2.CurrentBitmap = 1;
                    }
                    return;
                }
            }
        }

        private void OnCheckFPS(object sender, EventArgs e)
        {
            panel1.tb.Visible = !panel1.tb.Visible;
        }

        #endregion

        #region button event handlers
        private void OnClickSaveStateButton(object sender, EventArgs e)
        {
            using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"D:\EPAM lessons\IndividualProject\tests\Map.txt"))
            {
                int cnt = 0;
                foreach (ChipFull c in ElementMap.Instance.ColorMap.Peek())
                {
                    file.Write(String.Format("{0,15}, ", c.Chip.ElementColor.ToString()));
                    cnt++;
                    if (cnt > ElementMap.Instance.ColorMap.Peek().GetLength(0) - 1)
                    {
                        file.WriteLine();
                        cnt = 0;
                    }
                }
            }
        }

        private void OnClickUndoButton(object sender, EventArgs e)
        {
            ElementMap.Instance.Selection = new Point(-1, -1);
            
            if (ElementMap.Instance.ColorMap.Count > 2)
            {
                ElementMap.Instance.ColorMap.Pop();
            }
            ElementMap.Instance.MakeAllAccessible();
        }

        private void OnClickCheckSequenceButton(object sender, EventArgs e)
        {
            ElementMap.Instance.CheckSequences(ElementMap.Instance.ColorMap.Peek(), true, true, true);
        }

        private void OnClickDropButton(object sender, EventArgs e)
        {
            if (ElementMap.Instance.ColorMap.Count == 0)
            {
                MessageBox.Show("Map is empty");
            }
            else
            {
                ElementMap.Instance.DropElements(ElementMap.Instance.ColorMap.Peek(), true, true);
            }

        }
        //Generate new map
        private void OnClickGenerateNewButton(object sender, EventArgs e)
        {
            Thread th = new Thread(new ThreadStart(new Action(() =>
                {
                    ElementMap.Instance.OnGenerateNewMap(true, true);
                })));
            th.Start();
        }
        //check if there are available moves
        private void OnClickCheckForMovesButton(object sender, EventArgs e)
        {
            ElementMap.Instance.CheckForAvailableMoves(ElementMap.Instance.ColorMap.Peek());
        }

        private void OnClickRefreshButton(object sender, MouseEventArgs e)
        {
            if (ElementMap.Instance.ColorMap.Count > 0)
            {
                for (int i = 0; i < ElementMap.Instance.ColorMap.Peek().GetLength(0); i++)
                {
                    for (int j = 0; j < ElementMap.Instance.ColorMap.Peek().GetLength(0); j++)
                    {
                        ElementMap.Instance.PointMap[i, j] = ElementMap.Instance.initialPointMap[i, j];
                    }
                }
               this.Invalidate(true);
            }
        }


        #endregion button event handlers

        #region other methods

        private void OnPointsInfoChanged(Point p, int plus)
        {
            //Console.WriteLine("!!!");
            if (this.PointsInfoLabel.InvokeRequired)
            {
                this.PointsInfoLabel.BeginInvoke(new Action(() =>
                {
                    this.PointsInfoLabel.Text = String.Format("Points: {0}", ElementMap.Instance.LControl.PointScore);
                }
                    ));
            }
            else
            {
                this.PointsInfoLabel.Text = String.Format("Points: {0}", ElementMap.Instance.LControl.PointScore);
            }
            Task.Run(new Action(() =>
            {
                LabelAnimation la = new LabelAnimation(p, 255, plus.ToString());
                lock (this.panel1.LabelAnim)
                {
                    this.panel1.LabelAnim.Add(la);
                }
                la.PlusPointsAnim(1, 30);
                lock (this.panel1.LabelAnim)
                {
                    this.panel1.LabelAnim.Remove(la);
                }
            }
            ));
 
        }


        private void OnCrystalInfoChanged()
        {
            //Console.WriteLine("!!!");
            if (this.CrystalInfo.InvokeRequired)
            {
                this.CrystalInfo.BeginInvoke( new Action( () =>
                    {
                        this.CrystalInfo.Text = String.Format("Level = {0}\nCrystals = {1} / {2}", ElementMap.Instance.LControl.Level, ElementMap.Instance.LControl.CurrentCrystalScore, ElementMap.Instance.LControl.Level * 5);
                        this.progressBar2.Caption = String.Format("{0} / {1}", ElementMap.Instance.LControl.CurrentCrystalScore, ElementMap.Instance.LControl.Level * 5);
                        this.progressBar2.Value = ElementMap.Instance.LControl.CurrentCrystalScore;
                        if (ElementMap.Instance.LControl.CurrentCrystalScore >= ElementMap.Instance.LControl.Level * 5 && !ElementMap.Instance.MoveNoMore)
                        {
                            ElementMap.Instance.MoveNoMore = true;
                            MainTimer.Stop();
                            GoToNextLevel();
                        }
                    }
                    ));
            }
            else
            {
                this.CrystalInfo.Text = String.Format("Level = {0}\nCrystals = {1} / {2}", ElementMap.Instance.LControl.Level, ElementMap.Instance.LControl.CurrentCrystalScore, ElementMap.Instance.LControl.Level * 5);
                this.progressBar2.Caption = String.Format("{0} / {1}", ElementMap.Instance.LControl.CurrentCrystalScore, ElementMap.Instance.LControl.Level * 5);
                this.progressBar2.Value = ElementMap.Instance.LControl.CurrentCrystalScore;
                if (ElementMap.Instance.LControl.CurrentCrystalScore >= ElementMap.Instance.LControl.Level * 5 && !ElementMap.Instance.MoveNoMore)
                {
                    ElementMap.Instance.MoveNoMore = true;
                    MainTimer.Stop();
                    GoToNextLevel();
                }
            }
        }

        private void GoToNextLevel()
        {
            this.progressBar2.Value = this.progressBar2.Maximum;
            MessageBox.Show("Level completed. Generating next level.");
            bool areAllNotBusy = false;
            while (!areAllNotBusy)
            {
                areAllNotBusy = true;
                foreach (bool chip in ElementMap.Instance.BusyMap)
                {
                    if (chip)
                    {
                        areAllNotBusy = false;
                        break;
                    }
                }
            }
            ElementMap.Instance.LControl.CurrentCrystalScore = 0;
            ElementMap.Instance.LControl.Level++;

            panel1.Reset1Performed = false;
            panel1.Reset2Performed = false;

            this.progressBar2.Maximum = ElementMap.Instance.LControl.Level * 5;
            this.progressBar2.Value = 0;
            this.progressBar2.Caption = String.Format("0 / {0}", ElementMap.Instance.LControl.Level * 5);

            this.CrystalInfo.Text = String.Format("Level = {0}\nCrystals = {1} / {2}", ElementMap.Instance.LControl.Level, ElementMap.Instance.LControl.CurrentCrystalScore, ElementMap.Instance.LControl.Level * 5);
            OnClickGenerateNewButton(null, null);
        }

        #endregion
        
        #region Timers events handlers
        
        private void FpsTimer_Tick(object sender, EventArgs e)
        {
            panel1.FPSText = String.Format("{0}", panel1.FPSCounter);
            panel1.FPSCounter = 0;
        }

        private void MainTimer_Tick(object sender, EventArgs e)
        {
            if (++this.progressBar1.Value >= this.progressBar1.Maximum)
            {
                ElementMap.Instance.MoveNoMore = true;
                MainTimer.Stop();
                MessageBox.Show("Time is up. Try again!");

                this.progressBar2.Value = 0;

                bool areAllNotBusy = false;
                while (!areAllNotBusy)
                {
                    areAllNotBusy = true;
                    foreach (bool chip in ElementMap.Instance.BusyMap)
                    {
                        if (chip)
                        {
                            areAllNotBusy = false;
                            break;
                        }
                    }
                }
                ElementMap.Instance.LControl.CurrentCrystalScore = 0;

                panel1.Reset1Performed = false;
                panel1.Reset2Performed = false;

                this.progressBar2.Maximum = ElementMap.Instance.LControl.Level * 5;
                this.progressBar2.Value = 0;
                this.progressBar2.Caption = String.Format("0 / {0}", ElementMap.Instance.LControl.Level * 5);

                this.CrystalInfo.Text = String.Format("Level = {0}\nCrystals = {1} / {2}", ElementMap.Instance.LControl.Level, ElementMap.Instance.LControl.CurrentCrystalScore, ElementMap.Instance.LControl.Level * 5);
                OnClickGenerateNewButton(null, null);
            }
        }
        #endregion

    }

    public struct ThreadData
    {
        public Point element1Index;
        public Point element2Index;
        public bool rollback;
    }

}